/*
 * $Id: Type1Font.java,v 1.2 2007/12/20 18:33:31 rbair Exp $
 *
 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
 * Santa Clara, California 95054, U.S.A. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

package com.sun.pdfview.font;

import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;

import com.sun.pdfview.PDFFile;
import com.sun.pdfview.PDFObject;

/**
 * A representation, with parser, of an Adobe Type 1 font.
 * @author Mike Wessler
 */
public class Type1Font extends OutlineFont {
    String chr2name[];
    int password;
    byte[] subrs[];
   
    int lenIV;
    
    Map name2outline;
    Map name2width;
    
    AffineTransform at;
    
    /** the Type1 stack of command values */
    float stack[]= new float[100];
    /** the current position in the Type1 stack */
    int sloc= 0;
    
    /** the stack of postscript commands (used by callothersubr) */
    float psStack[] = new float[3];
    /** the current position in the postscript stack */
    int psLoc = 0;
    
    /**
     * create a new Type1Font based on a font data stream and an encoding.
     * @param baseName the postscript name of this font
     * @param src the Font object as a stream with a dictionary
     * @param descriptor the descriptor for this font
     */
    public Type1Font(String baseName, PDFObject src,
                     PDFFontDescriptor descriptor) throws IOException {
        super(baseName, src,  descriptor);

        if (descriptor != null && descriptor.getFontFile() != null) {
            // parse that file, filling name2outline and chr2name
            int start= descriptor.getFontFile().getDictRef("Length1").getIntValue();
            int len= descriptor.getFontFile().getDictRef("Length2").getIntValue();
            byte font[]= descriptor.getFontFile().getStream();
        
            parseFont(font, start, len);    
        }
    }
       
    /** Read a font from it's data, start position and length */
    protected void parseFont(byte[] font, int start, int len) {
        name2width = new HashMap();
        
	byte data[] = null;
        
        if (isASCII(font, start)) {
            byte[] bData = readASCII(font, start, start + len);
            data = decrypt(bData, 0, bData.length, 55665, 4);
        } else {
            data = decrypt(font, start, start+len, 55665, 4);
	}
        
        // encoding is in cleartext area
	chr2name= readEncoding(font);
	int lenIVLoc= findSlashName(data, "lenIV");
	PSParser psp= new PSParser(data, 0);
	if (lenIVLoc<0) {
	    lenIV= 4;
	} else {
	    psp.setLoc(lenIVLoc+6);
	    lenIV= Integer.parseInt(psp.readThing());
	}
	password= 4330;
	int matrixloc= findSlashName(font, "FontMatrix");
	if (matrixloc<0) {
	    System.out.println("No FontMatrix!");
	    at= new AffineTransform(0.001f, 0, 0, 0.001f, 0, 0);
	} else {
	    PSParser psp2= new PSParser(font, matrixloc+11);
	    // read [num num num num num num]
	    float xf[]= psp2.readArray(6);
	    //	    System.out.println("FONT MATRIX: "+xf);
	    at= new AffineTransform(xf);
	}
	
        subrs= readSubrs(data);
        name2outline = new TreeMap(readChars(data));
	// at this point, name2outline holds name -> byte[].
    }

    /**
     * parse the encoding portion of the font definition
     * @param d the font definition stream
     * @return an array of the glyphs corresponding to each byte
     */
    private String[] readEncoding(byte[] d) {
	byte[][] ary= readArray(d, "Encoding", "def");
	String res[]= new String[256];
	for (int i=0; i<ary.length; i++) {
	    if (ary[i]!=null) {
		if (ary[i][0]=='/') {
		    res[i]= new String(ary[i]).substring(1);
		} else {
		    res[i]= new String(ary[i]);
		}
	    } else {
		res[i]= null;
	    }
	}
	return res;
    }

    /**
     * read the subroutines out of the font definition
     * @param d the font definition stream
     * @return an array of the subroutines, each as a byte array.
     */
    private byte[][] readSubrs(byte[] d) {
	return readArray(d, "Subrs", "index");
    }
    
    /**
     * read a named array out of the font definition.
     * <p>
     * this function attempts to parse an array out of a postscript
     * definition without doing any postscript.  It's actually looking
     * for things that look like "dup <i>id</i> <i>elt</i> put", and
     * placing the <i>elt</i> at the <i>i</i>th position in the array.
     * @param d the font definition stream
     * @param key the name of the array
     * @param end a string that appears at the end of the array
     * @return an array consisting of a byte array for each entry
     */
    private byte[][] readArray(byte[] d, String key, String end) {
	int i= findSlashName(d, key);
	if (i<0) {
	    // not found.
	    return new byte[0][];
	}
	// now find things that look like "dup id elt put"
	// end at "def"
	PSParser psp= new PSParser(d, i);
	psp.readThing();     // read the key (i is the start of the key)
	double val;
	String type= psp.readThing();
	if (type.equals("StandardEncoding")) {
	  byte[] stdenc[]= new byte[FontSupport.standardEncoding.length][];
	  for (i=0; i<stdenc.length; i++) {
	    stdenc[i]= FontSupport.getName(FontSupport.standardEncoding[i]).getBytes();
	  }
	  return stdenc;
	}
	int len= Integer.parseInt(type);
	byte[] out[]= new byte[len][];
	byte[] line;
	while (true) {
	    String s= psp.readThing();
	    if (s.equals("dup")) {
		int id= Integer.parseInt(psp.readThing());
		String elt= psp.readThing();
		line= elt.getBytes();
		if (Character.isDigit(elt.charAt(0))) {
		    int hold= Integer.parseInt(elt);
		    String special= psp.readThing();
		    if (special.equals("-|") || special.equals("RD")) {
			psp.setLoc(psp.getLoc()+1);
			line= psp.getNEncodedBytes(hold, password, lenIV);
		    }
		}
		out[id]= line;
	    } else if (s.equals(end)) {
		break;
	    }
	}
	return out;
    }

    /**
     * decrypt an array using the Adobe Type 1 Font decryption algorithm.
     * @param d the input array of bytes
     * @param start where in the array to start decoding
     * @param end where in the array to stop decoding
     * @param key the decryption key
     * @param skip how many bytes to skip initially
     * @return the decrypted bytes.  The length of this array will be
     * (start-end-skip) bytes long
     */
    private byte[] decrypt(byte[]d, int start, int end, int key, int skip) {
	if (end-start-skip<0) {
	    skip= 0;
	}
	byte[] o= new byte[end-start-skip];
	int r= key;
	int ipos;
	int c1= 52845;
	int c2= 22719;
	for(ipos= start; ipos<end; ipos++) {
	    int c= d[ipos]&0xff;
	    int p= (c ^ (r>>8))&0xff;
	    r= ((c+r)*c1+c2)&0xffff;
	    if (ipos-start-skip>=0) {
		o[ipos-start-skip]= (byte)p;
	    }
	}
	return o;
    }
   
    /**
     * Read data formatted as ASCII strings as binary data
     *
     * @param data the data, formatted as ASCII strings
     * @param start where in the array to start decrypting
     * @param end where in the array to stop decrypting
     */
    private byte[] readASCII(byte[] data, int start, int end) {
        // each byte of output is derived from one character (two bytes) of
        // input
        byte[] o = new byte[(end - start) / 2];
        
        int count = 0;
    	int bit = 0;
    
        for (int loc = start; loc < end ; loc ++) {
            char c = (char) (data[loc] & 0xff);
            byte b = (byte) 0;
            
            if (c >= '0' && c <= '9') {
                b = (byte) (c - '0');
            } else if (c >= 'a' && c <= 'f') {
                b = (byte) (10 + (c - 'a'));
            } else if (c >= 'A' && c <= 'F') {
                b = (byte) (10 + (c - 'A'));
            } else {
                // linefeed or something.  Skip.
                continue;
            }
           
            // which half of the byte are we?
            if ((bit++ % 2) == 0) {
                o[count] = (byte) (b << 4);
            } else {
                o[count++] |= b;
            }
        }
        
        return o;
    }

    /** 
     * Determine if data is in ASCII or binary format.  According to the spec,
     * if any of the first 4 bytes are not character codes ('0' - '9' or
     * 'A' - 'F' or 'a' - 'f'), then the data is binary.  Otherwise it is
     * ASCII
     */
    private boolean isASCII(byte[] data, int start) {
        // look at the first 4 bytes
        for (int i = start; i < start + 4; i++) {
            // get the byte as a character
            char c = (char) (data[i] & 0xff);
            
            if (c >= '0' && c <= '9') {
                continue;
            } else if (c >= 'a' && c <= 'f') {
                continue;
            } else if (c >= 'A' && c <= 'F') {
                continue;
            } else {
                // out of range
                return false;
            }
        }
        
        // all were in range, so it is ASCII
        return true;
    }
    
    /**
     * PostScript reader (not a parser, as the name would seem to indicate).
     */
    class PSParser {
	byte[] data;
	int loc;

	/**
	 * create a PostScript reader given some data and an initial offset
	 * into that data.
	 * @param data the bytes of the postscript information
	 * @param start an initial offset into the data
	 */
	public PSParser(byte[] data, int start) {
	    this.data= data;
	    this.loc= start;
	}

	/**
	 * get the next postscript "word".  This is basically the next
	 * non-whitespace block between two whitespace delimiters.
	 * This means that something like " [2 4 53]" will produce
	 * three items, while " [2 4 56 ]" will produce four.
	 */
	public String readThing() {
	    // skip whitespace
	    while (PDFFile.isWhiteSpace(data[loc])) {
		loc++;
	    }
	    // read thing
	    int start= loc;
	    while (!PDFFile.isWhiteSpace(data[loc])) {
		loc++;
	    }
	    String s= new String(data, start, loc-start);
	    //	    System.out.println("Read: "+s);
	    return s;
	}

	/**
	 * read a set of numbers from the input.  This method doesn't
	 * pay any attention to "[" or "]" delimiters, and reads any
	 * non-numeric items as the number 0.
	 * @param count the number of items to read
	 * @return an array of count floats
	 */
	public float[] readArray(int count) {
	    float[] ary= new float[count];
	    int idx= 0;
	    while (idx<count) {
		String thing= readThing();
		if (thing.charAt(0)=='[') {
		    thing= thing.substring(1);
		}
		if (thing.endsWith("]")) {
		    thing= thing.substring(0, thing.length()-1);
		}
		ary[idx++]= Float.valueOf(thing).floatValue();
	    }
	    return ary;
	}

	/**
	 * get the current location within the input stream
	 */
	public int getLoc() {
	    return loc;
	}

	/**
	 * set the current location within the input stream
	 */
	public void setLoc(int loc) {
	    this.loc= loc;
	}

	/**
	 * treat the next n bytes of the input stream as encoded
	 * information to be decrypted.
	 * @param n the number of bytes to decrypt
	 * @param key the decryption key
	 * @param skip the number of bytes to skip at the beginning of the
	 * decryption
	 * @return an array of decrypted bytes.  The length of the array
	 * will be n-skip.
	 */
	public byte[] getNEncodedBytes(int n, int key, int skip) {
	    byte[] result= decrypt(data, loc, loc+n, key, skip);
	    loc+= n;
	    return result;
	}
    }

    /**
     * get the index into the byte array of a slashed name, like "/name".
     * @param d the search array
     * @param name the name to look for, without the initial /
     * @return the index of the first occurance of /name in the array.
     */
    private int findSlashName(byte[] d, String name) {
	int i;
	for (i=0; i<d.length; i++) {
	    if (d[i]=='/') {
		// check for key
		boolean found= true;
		for (int j=0; j<name.length(); j++) {
		    if (d[i+j+1]!= name.charAt(j)) {
			found= false;
			break;
		    }
		}
		if (found) {
		    return i;
		}
	    }
	}
	return -1;
    }

    /**
     * get the character definitions of the font.
     * @param d the font data
     * @return a HashMap that maps string glyph names to byte arrays of
     * decoded font data.
     */
    private HashMap readChars(byte[] d) {
	// skip thru data until we find "/"+key
	HashMap hm= new HashMap();
	int i= findSlashName(d, "CharStrings");
	if (i<0) {
	    // not found
	    return hm;
	}
	PSParser psp= new PSParser(d, i);
	// read /name len -| [len bytes] |-
	// until "end"
	while (true) {
	    String s= psp.readThing();
	    char c= s.charAt(0);
	    if (c=='/') {
		int len= Integer.parseInt(psp.readThing());
		String go= psp.readThing();  // it's -| or RD
		if (go.equals("-|") || go.equals("RD")) {
		    psp.setLoc(psp.getLoc()+1);
		    byte[] line= psp.getNEncodedBytes(len, password, lenIV);
		    hm.put(s.substring(1), line);
		}
	    } else if (s.equals("end")) {
		break;
	    }
	}
	return hm;
    }

    
    /**
     * pop the next item off the stack
     */
    private float pop() {
	float val=0;
	if (sloc>0) {
	    val= stack[--sloc];
	}
	return val;
    }

    int callcount= 0;

    /**
     * parse glyph data into a GeneralPath, and return the advance width.
     * The working point is passed in as a parameter in order to allow
     * recursion.
     * @param cs the decrypted glyph data
     * @param gp a GeneralPath into which the glyph shape will be stored
     * @param pt a FlPoint object that will be used to generate the path
     * @param wid a FlPoint into which the advance width will be placed.
     */
    private void parse(byte[] cs, GeneralPath gp, FlPoint pt, FlPoint wid) {
	//	System.out.println("--- cmd length is "+cs.length);
	int loc=0;
	float x1,x2,x3,y1,y2,y3;
	while (loc<cs.length) {
	    int v= ((int)cs[loc++])&0xff;
	    if (v==255) {
		stack[sloc++]= ((((int)cs[loc])&0xff)<<24) +
		    ((((int)cs[loc+1])&0xff)<<16) +
		    ((((int)cs[loc+2])&0xff)<<8) +
		    ((((int)cs[loc+3])&0xff));
		loc+= 4;
//		System.out.println("Pushed long "+stack[sloc-1]);
	    } else if (v>=251) {
		stack[sloc++]= -((v-251)<<8)-(((int)cs[loc])&0xff)-108;
		loc++;
//		System.out.println("Pushed lo "+stack[sloc-1]);
	    } else if (v>=247) {
		stack[sloc++]= ((v-247)<<8)+(((int)cs[loc])&0xff)+108;
		loc++;
//		System.out.println("Pushed hi "+stack[sloc-1]);
	    } else if (v>=32) {
		stack[sloc++]= v-139;
//		System.out.println("Pushed "+stack[sloc-1]);
	    } else {
		//		System.out.println("CMD: "+v+" (stack is size "+sloc+")");
		switch(v) {
		case 0:   // x
		    throw new RuntimeException("Bad command ("+v+")");
		case 1:   // hstem
		    sloc= 0;
		    break;
		case 2:   // x
		    throw new RuntimeException("Bad command ("+v+")");
		case 3:   // vstem
		    sloc= 0;
		    break;
		case 4:   // y vmoveto
		    pt.y+= pop();
		    gp.moveTo(pt.x, pt.y);
		    sloc= 0;
		    break;
		case 5:   // x y rlineto
		    pt.y+= pop();
		    pt.x+= pop();
		    gp.lineTo(pt.x, pt.y);
		    sloc= 0;
		    break;
		case 6:   // x hlineto
		    pt.x+= pop();
		    gp.lineTo(pt.x, pt.y);
		    sloc= 0;
		    break;
		case 7:   // y vlineto
		    pt.y+= pop();
		    gp.lineTo(pt.x, pt.y);
		    sloc= 0;
		    break;
		case 8:   // x1 y1 x2 y2 x3 y3 rcurveto
		    y3=pop();
		    x3=pop();
		    y2=pop();
		    x2=pop();
		    y1=pop();
		    x1=pop();
		    gp.curveTo(pt.x+x1, pt.y+y1,
			       pt.x+x1+x2, pt.y+y1+y2,
			       pt.x+x1+x2+x3, pt.y+y1+y2+y3);
		    pt.x+= x1+x2+x3;
		    pt.y+= y1+y2+y3;
		    sloc= 0;
		    break;
		case 9:   // closepath
		    gp.closePath();
		    sloc=0;
		    break;
		case 10:  // n callsubr
		    int n= (int)pop();
		    if (subrs[n]==null) {
			System.out.println("No subroutine #"+n);
		    } else {
			callcount++;
			if (callcount>10) {
			    System.out.println("Call stack too large");
			    //			    throw new RuntimeException("Call stack too large");
			} else {
			    parse(subrs[n], gp, pt, wid);
			}
			callcount--;
		    }
		    break;
		case 11:  // return
		    return;
		case 12:  // ext...
		    v= ((int)cs[loc++])&0xff;
		    if (v == 6) {  // s x y a b seac
			char b = (char) pop();
			char a = (char) pop();
			float y = pop();
			float x = pop();
			buildAccentChar(x, y, a, b, gp);
			sloc= 0;
		    } else if (v==7) {  // x y w h sbw
			wid.y= pop();
			wid.x= pop();
			pt.y= pop();
			pt.x= pop();
			sloc= 0;
		    } else if (v==12) {  // a b div -> a/b
			float b= pop();
			float a= pop();
			stack[sloc++]= a/b;
		    } else if (v==33) {  // a b setcurrentpoint
			pt.y= pop();
			pt.x= pop();
			gp.moveTo(pt.x, pt.y);
			sloc= 0;
		    } else if (v==0) { // dotsection
			sloc=0;
		    } else if (v==1) {  // vstem3
			sloc= 0;
		    } else if (v==2) {  // hstem3
			sloc= 0;
		    } else if (v==16) {  // n callothersubr
			int cn = (int) pop();
			int countargs = (int) pop();
			
		//    System.out.println("Called othersubr with index "+cn);
                        
                        switch (cn) {
                            case 0:
                                // push args2 and args3 onto stack
                                psStack[psLoc++] = pop();
                                psStack[psLoc++] = pop();
                                pop();
                                break;
                            case 3:
                                // push 3 onto the postscript stack
                                psStack[psLoc++] = 3;
                                break;    
                            default:
                                // push arguments onto the postscript stack
                                for (int i = 0; i > countargs; i--) {
                                      psStack[psLoc++] = pop();
                                }
                                break;
                        }
		    } else if (v==17) {  // pop
		        // pop from the postscript stack onto the type1 stack
			stack[sloc++] = psStack[psLoc - 1];
                        psLoc--;
		    } else {
			throw new RuntimeException("Bad command ("+v+")");
		    }
		    break;
		case 13:  // s w hsbw
		    wid.x= pop();
		    wid.y= 0;
		    pt.x= pop();
		    pt.y= 0;
		    //		    gp.moveTo(pt.x, pt.y);
		    sloc= 0;
		    break;
		case 14:  // endchar
		    //		    return;
		    break;
		case 15:  // x
		case 16:  // x
		case 17:  // x
		case 18:  // x
		case 19:  // x
		case 20:  // x
		    throw new RuntimeException("Bad command ("+v+")");
		case 21:  // x y rmoveto
		    pt.y+= pop();
		    pt.x+= pop();
		    gp.moveTo(pt.x, pt.y);
		    sloc= 0;
		    break;
		case 22:  // x hmoveto
		    pt.x+= pop();
		    gp.moveTo(pt.x, pt.y);
		    sloc= 0;
		    break;
		case 23:  // x
		case 24:  // x
		case 25:  // x
		case 26:  // x
		case 27:  // x
		case 28:  // x
		case 29:  // x
		    throw new RuntimeException("Bad command ("+v+")");
		case 30:  // y1 x2 y2 x3 vhcurveto
		    x3= pop();
		    y2= pop();
		    x2= pop();
		    y1= pop();
		    x1=y3=0;
		    gp.curveTo(pt.x, pt.y+y1,
			       pt.x+x2, pt.y+y1+y2,
			       pt.x+x2+x3, pt.y+y1+y2);
		    pt.x+= x2+x3;
		    pt.y+= y1+y2;
		    sloc= 0;
		    break;
		case 31:  // x1 x2 y2 y3 hvcurveto
		    y3= pop();
		    y2= pop();
		    x2= pop();
		    x1= pop();
		    y1=x3=0;
		    gp.curveTo(pt.x+x1, pt.y,
			       pt.x+x1+x2, pt.y+y2,
			       pt.x+x1+x2, pt.y+y2+y3);
		    pt.x+= x1+x2;
		    pt.y+= y2+y3;
		    sloc= 0;
		    break;
		}
	    }
	}
    }

     /**
     * build an accented character out of two pre-defined glyphs.
     * @param x the x offset of the accent
     * @param y the y offset of the accent
     * @param a the index of the accent glyph
     * @param b the index of the base glyph
     * @param gp the GeneralPath into which the combined glyph will be
     * written.
     */
    private void buildAccentChar(float x, float y, char a, char b,
                                 GeneralPath gp) {        
        // get the outline of the accent
        GeneralPath pathA = getOutline(a, getWidth(a, null));
        
        try {
            // undo the effect of the transform applied in read 
            AffineTransform xformA = at.createInverse();
            xformA.translate(x, y);
            pathA.transform(xformA);
        } catch (NoninvertibleTransformException nte) {
            pathA.transform(AffineTransform.getTranslateInstance(x, y));
        }
        
        GeneralPath pathB = getOutline(b, getWidth(b, null));
        
        try {
            AffineTransform xformB = at.createInverse();
            pathB.transform(xformB);
        } catch (NoninvertibleTransformException nte) {
            // ignore
        }
        
        gp.append(pathB, false);
        gp.append(pathA, false);        
    }
    
    /** 
     * Get the width of a given character
     *
     * This method is overridden to work if the width array hasn't been
     * populated (as for one of the 14 base fonts)
     */
    public float getWidth(char code, String name) {
        // we don't have first and last chars, so therefore no width array
        if (getFirstChar() == -1 || getLastChar() == -1) {
            String key = chr2name[code & 0xff];
            
            // use a name if one is provided
            if (name != null) {
                key = name;
            }
  
            if (key != null && name2outline.containsKey(key)) {
                if (!name2width.containsKey(key)) {
                    // glyph has not yet been parsed
                    // getting the outline will force it to get read
                    getOutline(key, 0);
                }
                
                FlPoint width = (FlPoint) name2width.get(key);
                if (width != null) {   
                    return width.x / getDefaultWidth();
                }
            }
            
            return 0;
        }
        
        // return the width that has been specified
        return super.getWidth(code, name);
    }
    
    /**
     * Decrypt a glyph stored in byte form
     */
     private synchronized GeneralPath parseGlyph(byte[] cs, FlPoint advance,
            AffineTransform at)
     {
        GeneralPath gp = new GeneralPath();
	FlPoint curpoint = new FlPoint();
	
        sloc = 0;
	parse(cs, gp, curpoint, advance);
	
        gp.transform(at);
	return gp;   
     }
    
    /**
     * Get a glyph outline by name
     *
     * @param name the name of the desired glyph
     * @return the glyph outline, or null if unavailable
     */
    protected GeneralPath getOutline(String name, float width) {
        // make sure we have a valid name
        if (name == null || !name2outline.containsKey(name)) {
            name = ".notdef";
        }
        
        // get whatever is stored in name
        Object obj = name2outline.get(name);
        
        // if it's a byte array, it needs to be parsed
        // otherwise, just return the path
        if (obj instanceof GeneralPath) {
            return (GeneralPath) obj;
        } else {
            byte[] cs = (byte[]) obj;
            FlPoint advance = new FlPoint();
            
            GeneralPath gp = parseGlyph(cs, advance, at);
         
            if (width != 0 && advance.x != 0) {
                 // scale the glyph to fit in the width
                Point2D p = new Point2D.Float(advance.x, advance.y);
                at.transform(p, p);
            
                 double scale = width / p.getX();
                AffineTransform xform = AffineTransform.getScaleInstance(scale, 1.0);
                gp.transform(xform);
            }
            
            // put the parsed object in the cache
            name2outline.put(name, gp);
            name2width.put(name, advance);
            return gp;
        }
    }
    
    /**
     * Get a glyph outline by character code
     *
     * Note this method must always return an outline 
     *
     * @param src the character code of the desired glyph
     * @return the glyph outline
     */
    protected GeneralPath getOutline(char src, float width) {
        return getOutline(chr2name[src & 0xff], width);
    }
}

